抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

14. Ansible Playbook loop循环语句

目录

loop关键字说明

在ansible 2.5及以前的版本当中,所有的循环都是使用with_X风格。但是从2.6版本开始,官方开始推荐使用loop关键字来代替with_X风格的关键字。

在playbook中使用循环,直接使用loop关键字即可。

如下示例,启动httpd和postfix服务:

tasks:
  - name: postfix and httpd are running
    service:
      name: "{{ item }}"
      state: started
    loop:
      - postfix
      - httpd

那么在这个示例当中,其实就是使用loop代替了with_list循环。

事实上,我们可以使用loop关键字搭配一些过滤器来替换更多的、更复杂的with_X循环。

loop_control

loop_control用于在循环时,获取列表的索引

- hosts: test
  gather_facts: no
  vars:
    testlist:
      - a
      - [b,c,[e,f]]
      - d
  tasks:
    - debug:
        msg: "{{ index }}: {{ item }}"
      loop: "{{ testlist | flatten(levels=1) }}"
      loop_control:
        index_var: index

输出结果:

PLAY [localhost] ******************************************************************

TASK [debug] **********************************************************************
ok: [localhost] => (item=a) => {
    "msg": "0: a"
}
ok: [localhost] => (item=b) => {
    "msg": "1: b"
}
ok: [localhost] => (item=c) => {
    "msg": "2: c"
}
ok: [localhost] => (item=[u'e', u'f']) => {
    "msg": "3: [u'e', u'f']"
}
ok: [localhost] => (item=d) => {
    "msg": "4: d"
}

PLAY RECAP ************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

参数说明:

  • index_varloop_control的选项,让我们指定一个变量,loop_control会将列表元素的索引值存放到这个指定的变量中

with_list

loop可以替代with_list,当处理嵌套列表时,列表不会被拉平

- hosts: test
  gather_facts: no
  vars:
    testlist:
      - a
      - [b,c]
      - d
  tasks:
    - debug:
        msg: "{{ item }}"
      loop: "{{ testlist }}"

with_flattened

将所有嵌套都拉平

- hosts: test
  gather_facts: no
  vars:
    testlist:
      - a
      - [b,c]
      - d
  tasks:
    - debug:
        msg: "{{ item }}"
      loop: "{{ testlist| flatten }}"

with_items

只拉平第一层

- hosts: test
  gather_facts: no
  vars:
    testlist:
      - a
      - [b,c]
      - d
  tasks:
    - debug:
        msg: "{{ item }}"
      loop: "{{ testlist| flatten(levels=1) }}"

with_indexed_items

通过flatten过滤器(加参数),再配合loop_control关键字,可以替代with_indexed_items,当处理多层嵌套的列表时,只有列表中的第一层会被拉平

- hosts: test
  gather_facts: no
  vars:
    testlist:
      - a
      - [b,c]
      - d
  tasks:
    - debug:
        msg: "{{ index }}: {{ item }}"
      loop: "{{ testlist | flatten(levels=1) }}"
      loop_control:
        index_var: index

参数说明:

  • loop_control: 用于控制循环的行为,比如在循环时获取到元素的索引

  • index_varloop_control的选项,让我们指定一个变量,loop_control会将列表元素的索引值存放到这个指定的变量中

with_together

zip_longest过滤器配合list过滤器,可以替代with_together

- hosts: test
  gather_facts: no
  vars: 
    testlist1: [a,b]
    testlist2: [1,2,3]
    testlist3: [A,B,C,D]
  tasks:
    - debug:
        msg: "{{ item.0 }} -- {{ item.1 }} -- {{ item.2 }}"
        # [a,1,A],[b,2,B],['',3,C],['','',D]
      with_together:
        - "{{ testlist1 }}"
        - "{{ testlist2 }}"
        - "{{ testlist3 }}"
    - debug:   
        # [a,1,A],[b,2,B]
        msg: "{{ item.0 }} -- {{ item.1 }} -- {{ item.2 }}"
      loop: "{{ testlist1 | zip_longest(testlist2,testlist3) | list}}"

当多个列表使用with_together进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest过滤器也会像with_together一样,对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。

在使用zip_longest过滤器代替with_together关键字时,默认也是使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用”NONE”代替空值:

- debug:
    msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
  loop: "{{ testlist1 | zip_longest(testlist2,testlist3,filevalue='NONE') | list }}"

zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:

- debug:
    msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
  loop: "{{ testlist1 | zip(testlist2,testlist3) | list }}"

with_nested/with_cartesian

可使用product过滤器配合list过滤器以替代with_nested或者with_cartesian。product过滤器也是需要将组合后的数据进行列表化,所以需要与list过滤器配合使用:

- hosts: localhost
  gather_facts: no
  vars: 
    testlist1: [a,b,c]
    testlist2: [1,2,3,4]
  tasks:
    - debug:
        msg: "{{item.0}} --- {{ item.1}}"
      loop: "{{ testlist1 | product(testlist2) | list}}"


# 结果
ok: [localhost] => (item=[u'a', 1]) => {
    "msg": "a --- 1"
}
ok: [localhost] => (item=[u'a', 2]) => {
    "msg": "a --- 2"
}
ok: [localhost] => (item=[u'a', 3]) => {
    "msg": "a --- 3"
}
ok: [localhost] => (item=[u'a', 4]) => {
    "msg": "a --- 4"
}
ok: [localhost] => (item=[u'b', 1]) => {
    "msg": "b --- 1"
}
ok: [localhost] => (item=[u'b', 2]) => {
    "msg": "b --- 2"
}
ok: [localhost] => (item=[u'b', 3]) => {
    "msg": "b --- 3"
}
ok: [localhost] => (item=[u'b', 4]) => {
    "msg": "b --- 4"
}
ok: [localhost] => (item=[u'c', 1]) => {
    "msg": "c --- 1"
}
ok: [localhost] => (item=[u'c', 2]) => {
    "msg": "c --- 2"
}
ok: [localhost] => (item=[u'c', 3]) => {
    "msg": "c --- 3"
}
ok: [localhost] => (item=[u'c', 4]) => {
    "msg": "c --- 4"
}

with_sequence

使用range过滤器配合list过滤器可以替代with_sequence:

- hosts: localhost
  gather_facts: no
  tasks:
    - debug:
        msg: "{{ item }}"
      loop: "{{ range(0,6,2) | list }}"

# 结果
ok: [localhost] => (item=0) => {
    "msg": 0
}
ok: [localhost] => (item=2) => {
    "msg": 2
}
ok: [localhost] => (item=4) => {
    "msg": 4

上例中表示生成数字,从0开始,到6结束,步长为2。但是需要说明的是,range函数的操作不包含结束范围,也就是说上面的循环只会生成0,2,4三个数字,而不包含6。

另外,with_sequence还有格式化的功能:

- debug:
    msg: "{{ item }}"
  with_sequence: start=2 end=6 stride=2 format="number is %0.2f"

# 结果
ok: [localhost] => (item=number is 2.00) => {
    "msg": "number is 2.00"
}
ok: [localhost] => (item=number is 4.00) => {
    "msg": "number is 4.00"
}
ok: [localhost] => (item=number is 6.00) => {
    "msg": "number is 6.00"
}

可使用format配合loop实现:

- debug:
    msg: "{{ 'number is %0.2f' |format(item) }}"
  loop: "{{range(2,7,2) |list}}"

# 结果
ok: [localhost] => (item=number is 2.00) => {
    "msg": "number is 2.00"
}
ok: [localhost] => (item=number is 4.00) => {
    "msg": "number is 4.00"
}
ok: [localhost] => (item=number is 6.00) => {
    "msg": "number is 6.00"
}

with_random_choice

使用random函数可以替代with_random_choice,由于random函数是随机取出列表中的一个值,并不涉及循环操作,所以并不使用loop关键字:

- hosts: test
  gather_facts: no
  vars:
    testlist: [a,b,c]
  tasks:
    - debug:
        msg: "{{ testlist | random }}"

with_dict

可使用loop配合dict2items过滤器实现with_dict功能:

- hosts: test
  gather_facts: no
  vars:
    users:
      alice: female
      bob: male
  tasks:
    - debug:
        msg: "{{ item.key }} is {{ item.value}}"
      loop: "{{users | dict2items }}"

with_subelements

可使用loop配合subelements过滤器替代with_subelements:

- hosts: test
  gather_facts: no
  vars:
    users:
      - name: bob
        gender: male
        hobby:
          - Skateboard
          - VideoGame
      - name: alice
        gender: female
        hobby:
          - Music
  tasks:
    - debug:
        msg: "{{ item.0.name }}'s hobby is {{ item.1}}"
      with_subelements:
        - "{{ users }}"
        - hobby
    - debug:
        msg: "{{ item.0.name }}'s hobby is {{ item.1}}"
      loop: "{{ users | subelements('hobby') }}"

使用zip_longest过滤器将两个列表中的元素对齐合并

- hosts: localhost
  gather_facts: no
  vars: 
    testlist1: [a,b]    
    testlist2: [1,2,3]
    testlist3: [A,B,C,D]
  tasks:
    - debug:
        msg: "['{{ item.0 }}','{{ item.1 }}' ,'{{ item.2 }}']" # a,1,A   b,2,B '',3,c, '','',D
      loop: "{{ testlist1 | zip_longest(testlist2,testlist3) | list}}"

输出结果如下:

TASK [debug] **********************************************************************
ok: [localhost] => (item=[u'a', 1, u'A']) => {
    "msg": [
        "a", 
        "1", 
        "A"
    ]
}
ok: [localhost] => (item=[u'b', 2, u'B']) => {
    "msg": [
        "b", 
        "2", 
        "B"
    ]
}
ok: [localhost] => (item=[None, 3, u'C']) => {
    "msg": [
        "", 
        "3", 
        "C"
    ]
}
ok: [localhost] => (item=[None, None, u'D']) => {
    "msg": [
        "", 
        "", 
        "D"
    ]
}

当多个列表进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest过滤器会对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。

在使用zip_longest过滤器时,默认使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用”NONE”代替空值:

- debug:
    msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
  loop: {{ testlist1 | zip_longest(testlist2,testlist3,filevalue='NONE') | list }}

zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:

- debug:
    msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"   # a,1,A b,2,B 
  loop: {{ testlist1 | zip(testlist2,testlist3) | list }}

在循环语句中注册变量

下面是一个register的变量在循环中使用的例子:

# cat register_loop.yml 
- name: registered variable usage as a loop list
  hosts: test
  tasks:
      - name: ensure /mnt/bkspool exists
        file:
          path: /mnt/bkspool
          state: directory

      - name: retrieve the list of home directories
        command: ls /home
        register: home_dirs

      - name: Show home_dirs results
        debug:
          var: home_dirs.stdout_lines

      - name: add home dirs to the backup spooler
        file: 
          path: /mnt/bkspool/{{ item }}
          src: /home/{{ item }}
          state: link
          force: yes
        loop: "{{ home_dirs.stdout_lines }}"

在循环语句中注册变量:

- name: Loop Register test
  gather_facts: no
  hosts: test
  tasks:
    - name: Looping Echo Task
      shell: "echo this is my item: {{ item }}"
      loop:
        - one
        - two
      register: echo_results

    - name: Show echo_results variable
      debug:
        var: echo_results

执行语句,可以看到变量的返回结果为一个字典列表:

ok: [10.1.61.187] => {
    "echo_results": {
        "changed": true,
        "msg": "All items completed",
        "results": [
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "echo this is my item: one",
                "delta": "0:00:00.004905",
                "end": "2019-06-10 00:23:51.814151",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "echo this is my item: one",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true,
                        "warn": true
                    }
                },
                "item": "one",
                "rc": 0,
                "start": "2019-06-10 00:23:51.809246",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "this is my item: one",
                "stdout_lines": [
                    "this is my item: one"
                ]
            },
            {
                "ansible_loop_var": "item",
                "changed": true,
                "cmd": "echo this is my item: two",
                "delta": "0:00:00.004736",
                "end": "2019-06-10 00:23:52.008981",
                "failed": false,
                "invocation": {
                    "module_args": {
                        "_raw_params": "echo this is my item: two",
                        "_uses_shell": true,
                        "argv": null,
                        "chdir": null,
                        "creates": null,
                        "executable": null,
                        "removes": null,
                        "stdin": null,
                        "stdin_add_newline": true,
                        "strip_empty_ends": true,
                        "warn": true
                    }
                },
                "item": "two",
                "rc": 0,
                "start": "2019-06-10 00:23:52.004245",
                "stderr": "",
                "stderr_lines": [],
                "stdout": "this is my item: two",
                "stdout_lines": [
                    "this is my item: two"
                ]
            }
        ]
    }
}

评论